export class AudioCanvas {
  temp: typeof CanvasRenderingContext2D.prototype.createLinearGradient
  __ref: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;
  $gColor: CanvasGradient;
  $gLower: CanvasGradient;

  constructor(canvasRef) {
    /**
     * @tool 绘制模块
     *
     * @function copy 复制当前画布
     * @function init 初始化
     * @function drawWave 根据配置绘制矩形
     * @function drawWave 根据频率数组绘制波形图像
     *
     * @param __ref Canvas元素dom节点
     * @param ctx canvas画布上下文
     */
    this.__ref = canvasRef;
    this.ctx = canvasRef.getContext("2d");
  }

  copy() {
    const copyCv = document.createElement("canvas");
    copyCv.width = this.__ref.width;
    copyCv.height = this.__ref.height;
    copyCv.getContext("2d")?.drawImage(this.__ref, 0, 0);
    return copyCv;
  }

  init() {
    this.ctx.fillStyle = "white";
    const _w = this.__ref.width;
    const _h = this.__ref.height;
    this.ctx.fillRect(0, 0, _w, _h);

    const { ctx, __ref } = this;

    const gColor = ctx.createLinearGradient(0, __ref.height, __ref.width, 0);
    gColor.addColorStop(0, "#feac5e");
    gColor.addColorStop(0.5, "#c779d0");
    gColor.addColorStop(1, "#4bc0c8");
    this.$gColor = gColor;

    const gLower = ctx.createLinearGradient(0, __ref.height, __ref.width, 0);
    gLower.addColorStop(0, "#fdeff9");
    gLower.addColorStop(0.5, "#7303c0");
    gLower.addColorStop(1, "#03001e");
    this.$gLower = gLower;
  }

  drawRect(color, x = 0, y = 0, w = 0, h = 0) {
    this.ctx.fillStyle = color;
    this.ctx.fillRect(x, y, w, h);
  }

  drawWave(f) {
    const { ctx, __ref, $gColor, $gLower } = this;
    const { width, height } = __ref;
    
    // 清空画布
    ctx.clearRect(0, 0, width, height);

    const len = Math.floor(width / 5 / 2) + 1;
    const max = Math.max(...f) || 10;
    const min = Math.min(...f) || 5;
    const fixedParam = height / (2 * (max - min));
    // padding decide the initial height of the rect
    const padding = 60;
    const downH = height / 2 - 1;

    for (let i = len - 1; i > 0; i--) {
      const rectH = Math.floor(f[i] * fixedParam) - padding;
      const subRectH = rectH > 3 * padding ? rectH - 3 * padding : 0;
      const upH = height / 2 - rectH;
      const subUpH = upH + 3 * padding;

      this.drawRect($gColor, width / 2 + i * 5, upH, 3, rectH);
      this.drawRect($gColor, width / 2 + i * 5, downH, 3, rectH);
      this.drawRect($gLower, width / 2 + i * 5, subUpH, 3, subRectH);
      this.drawRect($gLower, width / 2 + i * 5, downH, 3, subRectH);
    }

    for (let i = 0; i < len; i++) {
      const rectH = Math.floor(f[len - 1 - i] * fixedParam) - padding;
      const subRectH = rectH > 3 * padding ? rectH - 3 * padding : 0;
      const upH = height / 2 - rectH;
      const subUpH = upH + 3 * padding;

      this.drawRect($gColor, i * 5, upH, 3, rectH);
      this.drawRect($gColor, i * 5, downH, 3, rectH);
      this.drawRect($gLower, i * 5, subUpH, 3, subRectH);
      this.drawRect($gLower, i * 5, downH, 3, subRectH);
    }
  }
}

export class AudioSolve {

  __ref: HTMLAudioElement;
  __audio: AudioContext;
  __analyser: AnalyserNode;
  __audioSrc: MediaElementAudioSourceNode;
  __buffer: AudioBufferSourceNode;
  __f: Uint8Array;
  __canvas: AudioCanvas;
  progress: null | number;

  constructor(audioRef) {
    /**
     * @tool 音频处理模块
     *
     * @function initAudio 初始化音频流程
     * @function update 更新频率数组， 通知canvas模块图像更新
     * @function connect 将canvas画布绑定到音频处理模块
     * @function destory 回收实例
     *
     * @param __id 节点id
     * @param __element 节点dom对象
     * @param __audio 音频实例
     * @param __analyser 分析器实例
     * @param __audioSrc 音频源 对象
     */
    this.__ref = audioRef;
    this.__audio = new AudioContext();
    this.__analyser = this.__audio.createAnalyser();
    this.__analyser.fftSize = 2048;
    this.__audioSrc = this.__audio.createMediaElementSource(this.__ref);

    this.__buffer = this.__audio.createBufferSource();
  }

  initAudio() {
    const { __audioSrc, __analyser, __audio } = this;

    // 将音频源与分析器连接
    __audioSrc.connect(__analyser);

    // 分析器与出口连接;
    __analyser.connect(__audio.destination);

    this.__f = new Uint8Array(__analyser.frequencyBinCount);

    this.progress = null;
  }

  update() {
    const { __analyser, __canvas, __f } = this;

    this.progress = requestAnimationFrame(() => {
      this.update();
    });

    __analyser.getByteFrequencyData(__f);

    __canvas && __canvas.drawWave(__f);
  }

  connect(canvas: AudioCanvas) {
    this.__canvas = canvas;
  }

  destory() {
    this.progress && cancelAnimationFrame(this.progress);
    this.__audio.close();
  }
}

export class VisualAudio {

  __canvas: AudioCanvas;
  __solver: AudioSolve;

  playList: string[];
  playIndex: number;
  eventCallBack: {
    finish: (() => void)[];
    start: (() => void)[];
  }

  $oncanplay: () => void;
  $onended: () => void;

  /**
   * @body 可视化音频模块
   *
   * @function open 设置宽高、读入音频、刷新Canvas
   * @function close 回收实例
   * @function resize resize画布 重新渲染
   *
   * @param __canvas 绘制模块实例
   * @param __solver 音频处理模块实例
   */
  constructor(canvas, audio, playList: string[]) {
    this.__canvas = new AudioCanvas(canvas.current);
    this.__solver = new AudioSolve(audio.current);
    this.__solver.connect(this.__canvas);
    this.eventCallBack = {
      finish: [],
      start: [],
    }
    this.playList = playList;
    this.playIndex = 0;
  }

  loadResource() {
    const { __solver, playList, playIndex } = this;
    const { __ref } = __solver;
    __ref.src = playList[playIndex];
    this.$oncanplay = () => {
      // 如果不是第一首歌则自动续播
      playIndex && __ref.play();
    }
    this.$onended = () => {
      console.log('player finish one music, index is: ', playIndex);
      __ref.removeEventListener('canplay', this.$oncanplay);
      __ref.removeEventListener('ended', this.$onended);
      if ((playIndex + 1) === playList.length) {
        console.log('all music played');
        // 执行回调
        this.eventCallBack.finish.forEach((fn) => fn());
      } else {
        this.playIndex += 1;
        this.loadResource();
      }
    }

    __ref.addEventListener('canplay', this.$oncanplay);
    __ref.addEventListener('ended', this.$onended);

    __ref.load();
  }

  addResource(src: string) {
    this.playList.push(src);
  }

  shiftResource() {
    if (this.playIndex && this.playList.length) {
      // 如果删除资源不会导致越界，则可以直接删除
      this.playList.shift();
      this.playIndex -= 1;
    }
  }

  addEventListener(event: 'finish' | 'start', fn) {
    this.eventCallBack[event].push(fn);
  }

  removeEventListener(event: 'finish' | 'start', fn) {
    this.eventCallBack[event] = this.eventCallBack[event].filter((oldFn) => oldFn !== fn);
  }

  open() {
    this.resize();
    this.__canvas.init();
    this.__solver.initAudio();
    this.__solver.update();
    this.loadResource();
  }

  close() {
    this.__solver.destory();
  }

  resize() {
    const copy = this.__canvas.copy();
    const canvas = this.__canvas.__ref;
    const bound = canvas.getBoundingClientRect();
    canvas.width = bound.width;
    canvas.height = bound.height;
    this.__canvas.ctx.drawImage(copy, 0, 0);
  }
}